import 'dart:math';
import 'dart:ui';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

import '../common/coordinate.dart';

class Paper extends StatefulWidget {
  const Paper({super.key});

  @override
  State<Paper> createState() => _PaperState();
}

class _PaperState extends State<Paper> with SingleTickerProviderStateMixin {
  late AnimationController _ctrl;

  @override
  void initState() {
    super.initState();
    _ctrl =
        AnimationController(duration: const Duration(seconds: 3), vsync: this)
          ..forward();
  }

  @override
  void dispose() {
    _ctrl.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: CustomPaint(
        painter: PaperPainter(progress: _ctrl),
      ),
    );
  }
}

class PaperPainter extends CustomPainter {
  final Animation<double> progress;

  PaperPainter({required this.progress}) : super(repaint: progress);

  final Paint mPaint = Paint()
    ..style = PaintingStyle.fill
    ..color = Colors.deepPurpleAccent;
  final Path mPath = Path();
  final CoordinateByPath coordinate = CoordinateByPath();

  @override
  void paint(Canvas canvas, Size size) {
    coordinate.paint(canvas, size);
    // 原点坐标移动到屏幕中心
    canvas.translate(size.width / 2, size.height / 2);

    //testClose(canvas);
    //testContains(canvas);
    //testTransform(canvas);
    //testCombine(canvas);
    testComputeMetrics(canvas);
  }

  /// close、reset、shift
  // path#close ：用于将路径尾点和起点，进行路径封闭。
  // path#reset ：用于将路径进行重置，清除路径内容。
  // path#shift ：指定点Offset将路径进行平移，且返回一条新的路径
  void testClose(Canvas canvas) {
    resetPathPaint();

    mPath
      ..lineTo(100, 100)
      ..relativeLineTo(0, -50)
      ..close();
    canvas.drawPath(mPath, mPaint);
    canvas.drawPath(mPath.shift(const Offset(100, 0)), mPaint);
  }

  /// contains和getBounds
  // Paint#contains可以判断点Offset在不在路径之内(如下图紫色区域)，
  // 这是个非常好用的方法，可以根据这个方法做一些触点判断或简单的碰撞检测。
  // Paint#getBounds可以获取当前路径所在的矩形区域，(如下橙色区域)
  void testContains(Canvas canvas) {
    resetPathPaint();
    mPaint
      ..color = Colors.purple
      ..style = PaintingStyle.fill;

    mPath
      ..relativeMoveTo(0, 0)
      ..relativeLineTo(-30, 120)
      ..relativeLineTo(30, -30)
      ..relativeLineTo(30, 30)
      ..close();
    canvas.drawPath(mPath, mPaint);

    print(mPath.contains(const Offset(20, 20)));
    print(mPath.contains(const Offset(0, 20)));

    Rect bounds = mPath.getBounds();
    canvas.drawRect(
        bounds,
        mPaint
          ..color = Colors.orange
          ..style = PaintingStyle.stroke);
  }

  /// Path#transform: 路径变换
  // 对于对称性图案，当已经有一部分单体路径，可以根据一个4*4的矩阵对路径进行变换。
  // 可以使用Matrix4对象进行辅助生成矩阵。能很方便进行旋转、平移、缩放、斜切等变换效果。
  void testTransform(Canvas canvas) {
    resetPathPaint();
    mPaint
      ..color = Colors.purple
      ..style = PaintingStyle.fill;

    mPath
      ..relativeMoveTo(0, 0)
      ..relativeLineTo(-30, 120)
      ..relativeLineTo(30, -30)
      ..relativeLineTo(30, 30)
      ..close();
    for (int i = 0; i < 8; i++) {
      canvas.drawPath(
          mPath.transform(Matrix4.rotationZ(i * pi / 4).storage), mPaint);
    }
  }

  /// combine: 路径联合
  // Path#combine用于结合两个路径，并生成新路径，可用于生成复杂的路径。
  // 一共有如下五种联合方式，效果如下图:
  void testCombine(Canvas canvas) {
    resetPathPaint();
    mPaint
      ..color = Colors.purple
      ..style = PaintingStyle.fill;

    mPath
      ..relativeMoveTo(0, 0)
      ..relativeLineTo(-30, 120)
      ..relativeLineTo(30, -30)
      ..relativeLineTo(30, 30)
      ..close();

    var pathOval = Path()
      ..addOval(
          Rect.fromCenter(center: const Offset(0, 0), width: 60, height: 60));
    canvas.drawPath(
        Path.combine(PathOperation.difference, mPath, pathOval), mPaint);

    canvas.translate(120, 0);
    canvas.drawPath(
        Path.combine(PathOperation.intersect, mPath, pathOval), mPaint);

    canvas.translate(120, 0);
    canvas.drawPath(Path.combine(PathOperation.union, mPath, pathOval), mPaint);

    canvas.translate(-120 * 3, 0);
    canvas.drawPath(
        Path.combine(PathOperation.reverseDifference, mPath, pathOval), mPaint);

    canvas.translate(-120, 0);
    canvas.drawPath(Path.combine(PathOperation.xor, mPath, pathOval), mPaint);
  }

  /// 路径测量的使用 computeMetrics
  // computeMetrics 是路径中一个非常实用的操作，可以根据这个方法获取很多有价值的信息，比如路径上某点在路径的位置、角度，路径长度等。
  // 通过这些和动画结合，可以做出环路经运动、路径绘制动画等效果。
  void testComputeMetrics(Canvas canvas) {
    // 通过path.computeMetrics()，可以获取一个可迭代PathMetrics类对象 它迭代出的是PathMetric对象，也就是每个路径的测量信息。
    // 也就是说通过path.computeMetrics()你获得是一组路径的测量信息。注意：如下path.addOval之后，PathMetrics类对象中元素变为两个。
    Path path = Path()
      ..relativeMoveTo(0, 0)
      ..relativeLineTo(-30, 120)
      ..relativeLineTo(30, -30)
      ..relativeLineTo(30, 30)
      ..close();
    path.addOval(Rect.fromCenter(center: Offset.zero, width: 50, height: 50));
    // 通过PathMetrics对象可以获得路径长度 length、路径索引 contourIndex 及 isClosed路径是否闭合isClosed。
    PathMetrics pms = path.computeMetrics();
    pms.forEach((element) {
      print(
          "---length:${element.length} ---contourIndex:${element.contourIndex} ---isClosed:${element.isClosed}");
      // 比如我想要在路径一半的地方绘制一个小球，如果通过自己计算的话，非常困难。
      // 幸运的是通过路径测量，实现起来就非常方便。甚至还能得到改点的角度、速度信息。
      // 下面通过pm.length * 0.5表示在路径长度50%的点的信息。
      //Tangent? t = element.getTangentForOffset(element.length * 0.5);
      // 添加动画
      Tangent? t = element.getTangentForOffset(element.length * progress.value);
      if (t == null) return;
      print(
          "---position:${t.position} ---angle:${t.angle} ---vector:${t.vector}");
      canvas.drawCircle(t.position, 5, Paint()..color = Colors.deepOrange);
    });

    canvas.drawPath(path, mPaint..style = PaintingStyle.stroke);
  }

  void resetPathPaint() {
    mPath.reset();
    mPaint
      ..color = Colors.purpleAccent
      ..strokeWidth = 2
      ..style = PaintingStyle.stroke;
  }

  @override
  bool shouldRepaint(covariant PaperPainter oldDelegate) =>
      oldDelegate.progress != progress;
}
